/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.slots.statistic.data;

import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.slots.statistic.MetricEvent;
import java.util.concurrent.atomic.LongAdder;

/**
 * 指标数据统计的实现
 *
 * 如果我们希望能够直到某个接口的每秒处理成功请求数、每秒处理失败请求数、以及处理每个成功请求的平均耗时，我们只需要控制 Bucket 统计一秒钟的指标数据即可，我们只需要控制 BUcket 统计一秒钟内的
 * 指标数据即可，但如何才能确保 Bucket 存储的就是精确到 1秒 内的数据呢？
 *
 * 最 Low 的做法就是启动一个定时任务每秒创建一个 Bucket，但统计出来的数据误差绝对很大，Sentinel 是这样实现的，他定义一个 Bucket 数组，根据时间戳来定位到数据的下标，假设我们需要统计每1秒钟
 * 处理的请求数等数据，且只需要保存最近一分钟的数据，那么 Bucket 数据的大小就可以设置为 60，每个 Bucket 的 windowLengthInMs（窗口时间） 大小就是 1000 毫秒，即1秒
 *
 * 由于每个 Bucket 存储的是 1秒的数据，假设 Bucket 数组的大小是无限大的，那么我们只需要将当前时间戳去掉毫秒部分就能得到当前的秒数就就能得到当前的秒数，将得到的秒速作为索引就能从 Bucket 数据钟获取当前
 * 时间窗口的 Bucket.
 *
 * 一切资源均有限，所以我我们不可能无限的存储 Bucket ，我们也不需要存储那么多历史数据在内存钟，当我们只需要保留一分钟的数据时，Bucket 数组的大小就可以设置为 60，我们希望这个数组可以循环使用，并且永远只保存
 * 最近1分钟的数据，这样不仅可以避免频繁的创建 Bucket ，也减少了资源的占用
 *
 *
 * Represents metrics data in a period of time span.
 *
 * @author jialiang.linjl
 * @author Eric Zhao
 */
public class MetricBucket {

    /**
     * 存储各事件的计数，比如异常总数、请求总数等
     *
     * Bucket 记录一段时间内的各项指标数据用的时一个 LongAdder 数组，
     * LongAdder 保证了数据修改的原子性，并且性能比 AtomicInteger 表现更好，数据的每个元素分别记录一个时间窗口内的请求总数、异常数、总耗时
     */
    private final LongAdder[] counters;

    /**
     * 这段时间内的最小耗时
     */
    private volatile long minRt;

    /**
     * 构造函数
     */
    public MetricBucket() {
        /**
         * 将限流结果枚举值遍历
         */
        MetricEvent[] events = MetricEvent.values();
        this.counters = new LongAdder[events.length];
        for (MetricEvent event : events) {
            counters[event.ordinal()] = new LongAdder();
        }

        /**
         * 初始化统计
         */
        initMinRt();
    }

    public MetricBucket reset(MetricBucket bucket) {
        for (MetricEvent event : MetricEvent.values()) {
            counters[event.ordinal()].reset();
            counters[event.ordinal()].add(bucket.get(event));
        }
        initMinRt();
        return this;
    }

    private void initMinRt() {
        this.minRt = SentinelConfig.statisticMaxRt();
    }

    /**
     * Reset the adders.
     *
     * @return new metric bucket in initial state
     */
    public MetricBucket reset() {
        for (MetricEvent event : MetricEvent.values()) {
            counters[event.ordinal()].reset();
        }
        initMinRt();
        return this;
    }

    /**
     * 当需要获取 Bucket 记录总的成功请求数 或 异常总数、总的请求处理耗时，可根据事件类型(MetricEvent) ,从 Bucket 的 LongAdder 数组中获取对应的
     * LongAdder 并调用 sum 方法获取总数，如下代码
     *
     * @param event
     * @return
     */
    public long get(MetricEvent event) {
        return counters[event.ordinal()].sum();
    }

    /**
     * 当需要 Bucket 记录一个成功请求或者一个异常请求、处理请求的耗时，可根据事件类型 (MetricEvent) 从 LongAdder 数组中获取对应的 LongAdder 并调用 add 方法
     *
     * @param event
     * @param n
     * @return
     */
    public MetricBucket add(MetricEvent event, long n) {
        counters[event.ordinal()].add(n);
        return this;
    }

    public long pass() {
        return get(MetricEvent.PASS);
    }

    public long occupiedPass() {
        return get(MetricEvent.OCCUPIED_PASS);
    }

    public long block() {
        return get(MetricEvent.BLOCK);
    }

    public long exception() {
        return get(MetricEvent.EXCEPTION);
    }

    public long rt() {
        return get(MetricEvent.RT);
    }

    public long minRt() {
        return minRt;
    }

    public long success() {
        return get(MetricEvent.SUCCESS);
    }

    public void addPass(int n) {
        add(MetricEvent.PASS, n);
    }

    public void addOccupiedPass(int n) {
        add(MetricEvent.OCCUPIED_PASS, n);
    }

    public void addException(int n) {
        add(MetricEvent.EXCEPTION, n);
    }

    public void addBlock(int n) {
        add(MetricEvent.BLOCK, n);
    }

    public void addSuccess(int n) {
        add(MetricEvent.SUCCESS, n);
    }

    public void addRT(long rt) {
        add(MetricEvent.RT, rt);

        // Not thread-safe, but it's okay.
        if (rt < minRt) {
            minRt = rt;
        }
    }

    @Override
    public String toString() {
        return "p: " + pass() + ", b: " + block() + ", w: " + occupiedPass();
    }
}
